# 异步IO
# CPU的速度远远快于磁盘、网络等IO,在一个线程中,cou执行代码的速度极快,然而,一旦遇到IO操作,
# 读写文件,发送网络数据时,就需要等待IO操作完成,才能继续进行下一步操作,这种情况称为同步IO
# 因为一个IO操作就阻塞了当前线程，导致其他代码无法执行，所以我们必须使用多线程或者多进程来并发执行代码，为多个用户服务。
# 每个用户都会分配一个线程，如果遇到IO导致线程被挂起，其他用户的线程不受影响。
# 多线程和多进程的模型虽然解决了并发问题，但是系统不能无上限地增加线程。由于系统切换线程的开销也很大，
# 所以，一旦线程数量过多，CPU的时间就花在线程切换上了，真正运行代码的时间就少了，结果导致性能严重下降。
# 另一种解决IO问题的方法是异步IO。当代码需要执行一个耗时的IO操作时，它只发出IO指令，并不等待IO结果，
# 然后就去执行其他代码了。一段时间后，当IO返回结果时，再通知CPU进行处理。
# 消息模型是如何解决同步IO必须等待IO操作这一问题的呢？当遇到IO操作时，代码只负责发出IO请求，不等待IO结果，
# 然后直接结束本轮消息处理，进入下一轮消息处理过程。当IO操作完成后，将收到一条“IO完成”的消息，处理该消息时就可以直接获取IO操作结果。

# 1. 协程 ,  又称微线程,纤程,英文Coroutine
# 子程序，或者称为函数，在所有语言中都是层级调用，比如A调用B，B在执行过程中又调用了C，C执行完毕返回，B执行完毕返回，最后是A执行完毕。
# 所以子程序调用是通过栈实现的，一个线程就是执行一个子程序。
# 子程序调用总是一个入口，一次返回，调用顺序是明确的。而协程的调用和子程序不同。
# 协程看上去也是子程序，但执行过程中，在子程序内部可中断，然后转而执行别的子程序，在适当的时候再返回来接着执行。
# 协程的特点在于是一个线程执行，那和多线程比，协程有何优势？
# 一是协程极高的执行效率。因为子程序切换不是线程切换，而是由程序自身控制，
# 因此，没有线程切换的开销，和多线程比，线程数量越多，协程的性能优势就越明显。
# 二是不需要多线程的锁机制，因为只有一个线程，也不存在同时写变量冲突，
# 在协程中控制共享资源不加锁，只需要判断状态就好了，所以执行效率比多线程高很多。
# 因为协程是一个线程执行，那怎么利用多核CPU呢？最简单的方法是多进程+协程，既充分利用多核，又充分发挥协程的高效率，可获得极高的性能。
# Python对协程的支持是通过generator实现的。
# 在generator中，我们不但可以通过for循环来迭代，还可以不断调用next()函数获取由yield语句返回的下一个值。
# 但是Python的yield不但可以返回一个值，它还可以接收调用者发出的参数。
def consumer():
    r = ''
    while True:
        n = yield r
        if not n:
            return
        print('[consumer] consuming %s...' % n)
        r = '200 ok'


def produce(c):
    # 首先调用c.send(None)启动生成器
    c.send(None)
    n = 0
    while n < 5:
        n += 1
        print('[produce] producing %s...' % n)
        r = c.send(n)
        print('[produce]consumer return;%s' % r)
    c.close()


# c = consumer()
# produce(c)
# 套用Donald Knuth的一句话总结协程的特点： “子程序就是协程的一种特例。”

# 2. asyncio
# asyncio是Python 3.4版本引入的标准库，直接内置了对异步IO的支持。
# asyncio的编程模型就是一个消息循环。我们从asyncio模块中直接获取一个EventLoop的引用，然后把需要执行的协程扔到EventLoop中执行，就实现了异步IO。
import asyncio


@asyncio.coroutine
def hello():
    print('hello world')
    # 异步调用aysncio.sleep(1)
    r = yield from asyncio.sleep(1)
    print('hello again')


def event_loop():
    # 获取EventLoop
    print('11111')
    loop = asyncio.get_event_loop()
    print('22222')
    # 执行coroutine
    loop.run_until_complete(hello())
    print('33333')
    loop.close()
    print('44444')


# event_loop()
# @asyncio.coroutine把一个generator标记为coroutine类型，然后，我们就把这个coroutine扔到EventLoop中执行。
# hello()会首先打印出Hello world!，然后，yield from语法可以让我们方便地调用另一个generator。由于asyncio.sleep()也是一个coroutine，所以线程不会等待asyncio.sleep()，而是直接中断并执行下一个消息循环。当asyncio.sleep()返回时，线程就可以从yield from拿到返回值（此处是None），然后接着执行下一行语句。
# 把asyncio.sleep(1)看成是一个耗时1秒的IO操作，在此期间，主线程并未等待，而是去执行EventLoop中其他可以执行的coroutine了，因此可以实现并发执行。

# 用Task封装两个coroutine试试：
import asyncio, threading


@asyncio.coroutine
def hello(num):
    print('hello world!(%s)' % threading.currentThread())
    yield from asyncio.sleep(num)
    print('hello again!(%s)' % threading.currentThread())


def task_list():
    loop = asyncio.get_event_loop()
    tasks = [hello(1), hello(3), hello(5)]
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()


# task_list()
# 由打印的当前线程名称可以看出，几个coroutine是由同一个线程并发执行的。

# 如果把asyncio.sleep()换成真正的IO操作，则多个coroutine就可以由一个线程并发执行。
# 我们用asyncio的异步网络连接来获取sina、sohu和163的网站首页：
import asyncio


@asyncio.coroutine
def wget(host):
    print('wget %s...' % host)
    connect = asyncio.open_connection(host, 80)
    reader, writer = yield from connect
    header = 'GET / HTTP/1.0\r\nHost: %s\r\n\r\n' % host
    writer.write(header.encode('utf-8'))
    yield from writer.drain()
    while True:
        line = yield from reader.readline()
        if line == b'\r\n':
            break
        print('%s header > %s' % (host, line.decode('utf-8').rstrip()))
        # ignore the body,close the socket
        writer.close()


def start():
    loop = asyncio.get_event_loop()
    tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']]
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()


# start()

# 3. async/await
# 用asyncio提供的@asyncio.coroutine可以把一个generator标记为coroutine类型，然后在coroutine内部用yield from调用另一个coroutine实现异步操作。
# 为了简化并更好地标识异步IO，从Python 3.5开始引入了新的语法async和await，可以让coroutine的代码更简洁易读。
# 请注意，async和await是针对coroutine的新语法，要使用新的语法，只需要做两步简单的替换：
# 把@asyncio.coroutine替换为async；
# 把yield from替换为await。
# 注意新语法只能用在Python 3.5以及后续版本
async def hello2():
    print("async Hello world!")
    r = await asyncio.sleep(1)
    print("async Hello again!")


# loop = asyncio.get_event_loop()
# # 执行coroutine
# loop.run_until_complete(hello2())
# loop.close()

# 4. aiohttp
# asyncio可以实现单线程并发IO操作。如果仅用在客户端，发挥的威力不大。如果把asyncio用在服务器端，例如Web服务器，由于HTTP连接就是IO操作，因此可以用单线程+coroutine实现多用户的高并发支持。
# asyncio实现了TCP、UDP、SSL等协议，aiohttp则是基于asyncio实现的HTTP框架。
# 安装： pip install aiohttp
# 编写一个HTTP服务器，分别处理以下URL:/ - 首页返回b'<h1>Index</h1>';  /hello/{name} - 根据URL参数返回文本hello, %s!。
# import asyncio
# from aiohttp import web
# routes = web.RouteTableDef()
# @routes.get('/')
# async def index(request):
#     await asyncio.sleep(0.5)
#     return web.Response(body=b'<h1>Index</h1>')
#
#
# async def hello(request):
#     await asyncio.sleep(0.5)
#     text = '<h1>hello,%s</h1>' % request.match_info['name']
#     return web.Response(body=text.encode('utf-8'))
#
#
# async def init(loop):
#     app = web.Application(loop=loop)
#     app.router.add_route('GET', '/', index)
#     app.router.add_route('GET', '/hello/{name}', hello)
#     print('Server started at http://127.0.0.1:8000...')
#     # srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000)
#     # return srv
#     runner = web.AppRunner(app)
#     await runner.setup()
#     site = web.TCPSite(runner, '127.0.0.1', 9999)
#     await site.start()
#
# loop = asyncio.get_event_loop()
# loop.run_until_complete(init(loop))
# loop.run_forever()
from aiohttp import web

routes = web.RouteTableDef()


@routes.get('/')
async def index(request):
    await asyncio.sleep(2)
    return web.json_response({
        'name': 'index'
    })


@routes.get('/about')
async def about(request):
    await asyncio.sleep(0.5)
    return web.Response(text="<h1>about us</h1>")


def init():
    app = web.Application()
    app.add_routes(routes)
    web.run_app(app)


init()

# 然后访问浏览器
# http://127.0.0.1:8080/
# http://127.0.0.1:8080/about